/**
 * Copyright 2012 Simple-Jdbc-Lightweight

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

package org.mlu.jdbc.simple.mapper;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.beanutils.BeanUtilsBean2;
import org.mlu.jdbc.simple.utils.JdbcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * This class is meant for legacy database or beans which to not conform to naming conventions.
 *
 * @author Mlungisi Sincuba
 * @since 1.0
 */
public class CustomBeanMapper {
    private static final Logger logger = LoggerFactory.getLogger(CustomBeanMapper.class);
    
    
    public <T> T mapRow(ResultSet rs, T bean, Class<T> type, Map<String, String> tableColumnClassFieldMap) throws SQLException {
        logger.info("Entering mapRow");
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        BeanUtilsBean2 bean2 = new BeanUtilsBean2();
        try {
            bean = type.newInstance();
            while (rs.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = JdbcUtils.lookupColumnName(metaData, i);
                    logger.trace("column name is: {}", columnName);
                    String propertyName = tableColumnClassFieldMap.get(columnName);
                    logger.trace("property name is: {}", propertyName);
                    Object value = JdbcUtils.getResultSetValue(rs, i);
                    logger.trace("property value is: {}", value);

                    bean2.setProperty(bean, propertyName, value);

                }
            }
        } catch (InstantiationException ex) {
            logger.error("Could not instantiate Object " + type, ex);
        } catch (IllegalAccessException ex) {
            logger.error("Could not access Object " + type, ex);
        } catch (InvocationTargetException ex) {
            logger.error("Could not invoke Object " + type, ex);
        }
        logger.info("Finished mapRow for object {}", bean);
        return bean;
    }
    
    public <T> List<T> mapBeanList(ResultSet rs, T bean, Class<T> type, Map<String, String> tableColumnClassFieldMap) throws SQLException {
        logger.info("Entering mapBeanList");
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        BeanUtilsBean2 bean2 = new BeanUtilsBean2();
        List<T> list = new ArrayList<T>();
        try {
            bean = type.newInstance();
            while (rs.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = JdbcUtils.lookupColumnName(metaData, i);
                    logger.trace("column name is: {}", columnName);
                    String propertyName = tableColumnClassFieldMap.get(columnName);
                    logger.trace("property name is: {}", propertyName);
                    Object value = JdbcUtils.getResultSetValue(rs, i);
                    logger.trace("property value is: {}", value);

                    bean2.setProperty(bean, propertyName, value);
                }
                list.add(bean);
            }
        } catch (InstantiationException ex) {
            logger.error("Could not instantiate Object " + type, ex);
        } catch (IllegalAccessException ex) {
            logger.error("Could not access Object " + type, ex);
        } catch (InvocationTargetException ex) {
            logger.error("Could not invoke Object " + type, ex);
        }
        logger.info("Finished mapBeanList for object list {}", list);
        return list;
    }
    
    public <T> List<T> mapBeanList(Connection c, String sql, Map<String, String> tableColumnClassFieldMap, Class<T> type, Object ...params) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        T bean = null;
        List<T> list = new ArrayList<T>();
        try {
            ps = getPreparedStatement(ps, c, sql, params);
            rs = ps.executeQuery();
            list = mapBeanList(rs, bean, type, tableColumnClassFieldMap);
        } catch (SQLException ex) {
            logger.error("queryForObject with params could not be run", ex);
        } finally {
            JdbcUtils.closeDbRecources(c, ps, rs);
        }
        return list;
    }
    
    public <T> T mapRow(Connection c, String sql, Class<T> type, Map<String, String> tableColumnClassFieldMap, Object ...params) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        T bean = null;
        try {
            ps = getPreparedStatement(ps, c, sql, params);
            rs = ps.executeQuery();
            bean = mapRow(rs, bean, type, tableColumnClassFieldMap);
        } catch (SQLException ex) {
            logger.error("queryForObject with params could not be run", ex);
        } finally {
            JdbcUtils.closeDbRecources(c, ps, rs);
        }
        return bean;
    }
    
    public <T> T mapRow(DataSource ds, String sql, Class<T> type, Map<String, String> tableColumnClassFieldMap, Object ...params) {
        Connection c = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        T bean = null;
        try {
            c = ds.getConnection();
            ps = getPreparedStatement(ps, c, sql, params);
            rs = ps.executeQuery();
            bean = mapRow(rs, bean, type, tableColumnClassFieldMap);
        } catch (SQLException ex) {
            logger.error("queryForObject with params could not be run", ex);
        } finally {
            JdbcUtils.closeDbRecources(c, ps, rs);
        }
        return bean;
    }

    private PreparedStatement getPreparedStatement(PreparedStatement ps, Connection c, String sql, Object[] params) throws SQLException {
        ps = c.prepareStatement(sql);
        int i = 1;
        for (Object o : params) {
            ps.setObject(i, o);
            i++;
        }
        return ps;
    }
}
